/*
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
 */
package erwiki.part.rssgenerator.internal.services;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.EList;
import org.elwiki.permissions.PagePermission;
import org.elwiki_data.PageAttachment;
import org.elwiki_data.PageContent;
import org.elwiki_data.WikiPage;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

import erwiki.api.BackgroundThreads;
import erwiki.api.BackgroundThreads.Actor;
import erwiki.api.WikiPartReference;
import erwiki.api.auth.AuthorizationManager;
import erwiki.api.auth.ISessionMonitor;
import erwiki.api.component.WikiComponent;
import erwiki.api.core.Engine;
import erwiki.api.core.WikiContext;
import erwiki.api.core.WikiSession;
import erwiki.api.diff.DifferenceManager;
import erwiki.api.event.EngineEvent;
import erwiki.api.exceptions.WikiException;
import erwiki.api.pages.PageManager;
import erwiki.api.render.RenderingManager;
import erwiki.api.rss.IFeed;
import erwiki.api.rss.RssGenerator;
import erwiki.api.rwc.RWC;
import erwiki.api.variables.VariableManager;
import erwiki.configuration.IWikiConfiguration;
import erwiki.part.rssgenerator.internal.AtomFeed;
import erwiki.part.rssgenerator.internal.Entry;
import erwiki.part.rssgenerator.internal.Feed;
import erwiki.part.rssgenerator.internal.RSS10Feed;
import erwiki.part.rssgenerator.internal.RSS20Feed;
import erwiki.part.rssgenerator.internal.RssActor;

/**
 * Default implementation for {@link RssGenerator}.
 *
 * {@inheritDoc}
 */
// FIXME: Limit diff and page content size.
//@formatter:off
@Component(
	name = "erwiki.RssGenerator",
	service = { RssGenerator.class, WikiComponent.class, EventHandler.class  },
	property = {
		EventConstants.EVENT_TOPIC + "=" + EngineEvent.Topic.ALL,
	},
	scope = ServiceScope.SINGLETON)
//@formatter:on
public class DefaultRssGenerator implements RssGenerator, WikiComponent, EventHandler {

	private static final Logger log = Logger.getLogger(DefaultRssGenerator.class);

	/** The RSS file to generate. */

	private boolean m_enabled = true;

	private static final int MAX_CHARACTERS = Integer.MAX_VALUE - 1;

	/**
	 * Constructs the RSS generator.
	 */
	public DefaultRssGenerator() {
		super();
	}

	// -- OSGi service handling ----------------------(start)--

	/** Stores configuration. */
	@Reference
	private IWikiConfiguration wikiConfiguration;

	@WikiPartReference
	private Engine m_engine;

	@WikiPartReference
	private ISessionMonitor sessionMonitor;

	@WikiPartReference
	private AuthorizationManager authorizationManager;

	@WikiPartReference
	private VariableManager variableManager;

	@WikiPartReference
	private PageManager pageManager;

	@WikiPartReference
	private BackgroundThreads backgroundThreads;

	@WikiPartReference
	private RenderingManager renderingManager;

	@WikiPartReference
	private DifferenceManager differenceManager;

	/** {@inheritDoc} */
	@Override
	public void initialize() throws WikiException {
		// nothing.
	}

	/**
	 * Starts the RSS generator & generator thread.
	 *
	 * @throws WikiException
	 */
	private void initializeStageTwo() throws WikiException {
		if (prefsIsRssGenerate()) {
			String pathRssFile = prefsRssFilePath();
			IPath path = (Platform.OS.isWindows()) ? IPath.forWindows(pathRssFile) : IPath.forPosix(pathRssFile);
			if (!path.isAbsolute()) {
				// относительные имена путей привязываются к корневому пути приложения.
				// TODO: привязать к workspace.
				path = IPath.fromOSString(m_engine.getRootPath()).append(path);
			}
			File rssFile = path.toFile();
			int rssInterval = prefsRssInterval();

			Actor rssActor = new RssActor(m_engine, rssFile);
			Thread rssThread = backgroundThreads.createThread("ErWiki RSS Generator", rssInterval, rssActor);
			rssThread.start();

			log.debug("RSS file will be at " + rssFile.getAbsolutePath());
			log.debug("RSS refresh interval (seconds): " + rssInterval);
		}
	}

	// -- OSGi service handling ------------------------(end)--

	/** Определяет, включена/нет генерация RSS. */
	protected boolean prefsIsRssGenerate() {
		return getPreference(RssGenerator.Prefs.RSS_GENERATE, Boolean.class);
	}

	protected String prefsRssFilePath() {
		return getPreference(RssGenerator.Prefs.RSS_FILENAME, String.class);
	}

	protected int prefsRssInterval() {
		return getPreference(RssGenerator.Prefs.RSS_INTERVAL, Integer.class);
	}

	protected String prefsChannelDescription() {
		return getPreference(RssGenerator.Prefs.RSS_CHANNEL_DESCRIPTION, String.class);
	}

	protected String prefsChannelLanguage() {
		return getPreference(RssGenerator.Prefs.RSS_CHANNEL_LANGUAGE, String.class);
	}

	private String getAuthor(final WikiPage page) {
		String author = page.getAuthor();
		if (author == null) {
			author = "An unknown author";
		}

		return author;
	}

	private String getAttachmentDescription(final PageAttachment att) {
		final String author = ":FVK:"; // :FVK: getAuthor( att );
		final StringBuilder sb = new StringBuilder();

		/*:FVK:
		if( att.getVersion() != 1 ) {
			//:FVK: sb.append( author ).append( " uploaded a new version of this attachment on " ).append( att.getLastModifiedDate() );
		} else {
			//:FVK: sb.append( author ).append( " created this attachment on " ).append( att.getLastModifiedDate() );
		}
		*/

		sb.append("<br /><hr /><br />").append("Parent page: <a href=\"")
				// :FVK: .append( m_engine.getURL( WikiContext.PAGE_VIEW, att.getParentName(), null ) )
				// :FVK: .append( "\">" ).append( att.getParentName() ).append( "</a><br />" )
				.append("Info page: <a href=\"").append(m_engine.getURL(WikiContext.PAGE_INFO, att.getName(), null))
				.append("\">").append(att.getName()).append("</a>");

		return sb.toString();
	}

	private String getPageDescription(final WikiPage page) {
		final StringBuilder buf = new StringBuilder();
		final String author = getAuthor(page);
		// :FVK: final WikiContext ctx = Wiki.context().create(m_engine, page);
		/*:FVK:
		if( page.getVersion() > 1 ) {
		    final String diff = WikiEngine.getDifferenceManager().getDiff( ctx,
		                                                        page.getVersion() - 1, // FIXME: Will fail when non-contiguous versions
		                                                                 page.getVersion() );

		    buf.append( author ).append( " changed this page on " ).append( page.getLastModifiedDate() ).append( ":<br /><hr /><br />" );
		    buf.append( diff );
		} else*/ {
			buf.append(author).append(" created this page on ").append(page.getLastModifiedDate())
					.append(":<br /><hr /><br />");
			buf.append(renderingManager.getHTML(page));
		}

		return buf.toString();
	}

	private String getEntryDescription(final WikiPage page) {
		final String res;
		if (page instanceof PageAttachment) {
			res = getAttachmentDescription((PageAttachment) page);
		} else {
			res = getPageDescription(page);
		}

		return res;
	}

	// FIXME: This should probably return something more intelligent
	@Deprecated
	private String getEntryTitle(final WikiPage page) {
		return ":FVK:"; // :FVK: page.getName() + ", version " + page.getVersion();
	}

	/** {@inheritDoc} */
	@Override
	public String generateFeed(List<PageContent> changed, String mode, String type) throws IllegalArgumentException {
		Feed feed = switch (type) {
		case ATOM -> new AtomFeed();
		case RSS20 -> new RSS20Feed();
		default -> new RSS10Feed();
		};

		feed.setMode(mode);

		return switch (mode) {
		case MODE_BLOG -> generateBlogRSS(changed, feed);
		case MODE_FULL -> generateFullWikiRSS(feed);
		case MODE_WIKI -> generateWikiPageRSS(changed, feed);
		default -> throw new IllegalArgumentException("Invalid value for feed mode: " + mode);
		};
	}

	/** {@inheritDoc} */
	@Override
	public synchronized boolean isEnabled() {
		return m_enabled;
	}

	/** {@inheritDoc} */
	@Override
	public synchronized void setEnabled(boolean enabled) {
		m_enabled = enabled;
	}

	/** {@inheritDoc} */
	@Override
	public String getRssFile() {
		String pathRssFile = prefsRssFilePath();
		return pathRssFile;
	}

	/** {@inheritDoc} */
	@Override
	public String generateFullWikiRSS(IFeed feed) {
		feed.setChannelTitle(m_engine.getApplicationName());
		feed.setFeedURL(wikiConfiguration.getBaseURL());
		feed.setChannelLanguage(prefsChannelLanguage());
		String channelDescription = getPreference(RssGenerator.Prefs.RSS_CHANNEL_DESCRIPTION, String.class);
		feed.setChannelDescription(channelDescription);

		Set<WikiPage> changed = pageManager.getRecentChanges();

		WikiSession session = sessionMonitor.createGuestSession(null);
		int items = 0;
		for (Iterator<WikiPage> i = changed.iterator(); i.hasNext() && items < 15; items++) {
			WikiPage page = i.next();

			// Check if the anonymous user has view access to this page.
			if (!authorizationManager.checkPermission(session, new PagePermission(page, PagePermission.VIEW_ACTION))) {
				// No permission, skip to the next one.
				continue;
			}

			String url;
			if (page instanceof PageAttachment) {
				url = m_engine.getURL(WikiContext.ATTACHMENT_DOGET, page.getName(), null);
			} else {
				url = m_engine.getURL(WikiContext.PAGE_VIEW, page.getName(), null);
			}

			Entry e = new Entry();
			e.setPage(page);
			e.setURL(url);
			e.setTitle(page.getName());
			e.setContent(getEntryDescription(page));
			e.setAuthor(getAuthor(page));

			feed.addEntry(e);
		}

		return feed.getString();
	}

	/** {@inheritDoc} */
	@Override
	public String generateWikiPageRSS(List<PageContent> changed, IFeed feed) {
		WikiPage wikiPage = RWC.INSTANCE.getPage();
		feed.setChannelTitle(m_engine.getApplicationName() + ": " + wikiPage.getName());
		feed.setFeedURL(m_engine.getURL(WikiContext.PAGE_VIEW, wikiPage.getId(), null));

		String language = variableManager.getVariable(PROP_CHANNEL_LANGUAGE);
		if (StringUtils.isNotBlank(language)) {
			feed.setChannelLanguage(language);
		} else {
			feed.setChannelLanguage(prefsChannelLanguage());
		}

		String channelDescription = variableManager.getVariable(PROP_CHANNEL_DESCRIPTION);
		if (StringUtils.isNotBlank(channelDescription)) {
			feed.setChannelDescription(channelDescription);
		} else {
			feed.setChannelDescription(prefsChannelDescription());
		}

		List<PageContent> listChanged = new ArrayList<PageContent>(changed);
		Comparator<PageContent> histInfoComparator = Comparator.comparing(PageContent::getCreationDate)
				.thenComparingInt(PageContent::getVersion).reversed();
		Collections.sort(listChanged, histInfoComparator);

		WikiPage page = RWC.INSTANCE.getPage();
		// int items = 0;
		// for (final Iterator<WikiPage> i = changed.iterator(); i.hasNext() && items < 15; items++) {
		for (PageContent iHistoryInfo : listChanged) {
//final WikiPage page = i.next();
			final Entry e = new Entry();
			e.setPage(page);
			String url;

			/*TODO::FVK: возможно этот код понадобится для ленты об прикреплениях...
			 * в JSPWiki здесь был код для определения типа "страниц",
			 * и соответственно генерировался ULL: attachment/page.
			if (page instanceof PageAttachment) {
				url = ":FVK:"; //:FVK: m_engine.getURL( WikiContext.PAGE_ATTACH, page.getName(), "version=" + page.getVersion() );
			} else { }
			*/
			url = m_engine.getURL(WikiContext.PAGE_VIEW, page.getId(), "version=" + iHistoryInfo.getVersion());

			// Unfortunately, this is needed because the code will again go through replacement conversion.
			url = url.replace("&amp;", "&");
			e.setURL(url);
			e.setTitle(page.getName() + ", version " + iHistoryInfo.getVersion());
			e.setContent(getHistoryDescription(iHistoryInfo));
			e.setAuthor(getAuthor(page));

			feed.addEntry(e);
		}

		return feed.getString();
	}

	private String getHistoryDescription(PageContent pageContent) {
		StringBuilder buf = new StringBuilder();
		String author = pageContent.getAuthor();

		WikiPage wikiPage = pageContent.getWikipage();
		if (pageContent.isFirstContent()) {
			// TODO: версию именно создания страницы - хранить отдельно от её контента.
			buf.append(author + " created this page on " + pageContent.getCreationDate() + ":<br /><hr /><br />");
			String htmlContent = renderingManager.getHTML(pageContent.getWikipage());
			buf.append(htmlContent);
		} else {
			EList<PageContent> pageContents = wikiPage.getPageContents();
			int prevIdx = pageContents.indexOf(pageContent) - 1;
			int prevVersion = pageContents.get(prevIdx).getVersion();
			int currentVersion = pageContent.getVersion();
			String diff = differenceManager.getDiff(prevVersion, currentVersion);
			buf.append(author + " changed this page on " + pageContent.getCreationDate() + ":<br /><hr /><br />");
			buf.append(diff);
		}

		return buf.toString();
	}

	/** {@inheritDoc} */
	@Override
	public String generateBlogRSS(List<PageContent> changed, IFeed feed) {
		if (log.isDebugEnabled()) {
			log.debug("Generating RSS for blog, size=" + changed.size());
		}

		WikiPage wikiPage = RWC.INSTANCE.getPage();
		String ctitle = variableManager.getVariable(PROP_CHANNEL_TITLE);
		if (ctitle != null) {
			feed.setChannelTitle(ctitle);
		} else {
			feed.setChannelTitle(m_engine.getApplicationName() + ":" + wikiPage.getName());
		}

		feed.setFeedURL(m_engine.getURL(WikiContext.PAGE_VIEW, wikiPage.getId(), null));

		String language = variableManager.getVariable(PROP_CHANNEL_LANGUAGE);
		if (language != null) {
			feed.setChannelLanguage(language);
		} else {
			feed.setChannelLanguage(prefsChannelLanguage());
		}

		String channelDescription = variableManager.getVariable(PROP_CHANNEL_DESCRIPTION);
		if (channelDescription != null) {
			feed.setChannelDescription(channelDescription);
		}

		List<PageContent> changed1 = Collections.emptyList();// :FVK: tmp.
		/*:FVK: скорее всего контент уже отсортирован, не получен ли он из WeblogPlugin ?
		Comparator<IHistoryInfo> histInfoComparator = Comparator.comparing(IHistoryInfo::getCreationDate)
				.thenComparingInt(IHistoryInfo::getVersion);
		Collections.sort(changed1, histInfoComparator);
		*/

		// int items = 0;
		// for (final Iterator<WikiPage> i = changed1.iterator(); i.hasNext() && items < 15; items++) {
		int counter = 0;
		for (PageContent iHistoryInfo : changed1) {
			counter++;
			if (counter > 15) {
				break;
			}

			WikiPage page = iHistoryInfo.getWikipage();
			Entry e = new Entry();
			e.setPage(page);
			String url;

			if (page instanceof PageAttachment) {
				url = m_engine.getURL(WikiContext.ATTACHMENT_DOGET, page.getName(), null);
			} else {
				url = m_engine.getURL(WikiContext.PAGE_VIEW, page.getName(), null);
			}

			e.setURL(url);

			// Title
			String pageText = page.getContentText();

			String title = "";
			int firstLine = pageText.indexOf('\n');

			if (firstLine > 0) {
				title = pageText.substring(0, firstLine).trim();
			}

			if (title.length() == 0) {
				title = page.getName();
			}

			// Remove wiki formatting
			while (title.startsWith("!")) {
				title = title.substring(1);
			}

			e.setTitle(title);

			// Description
			if (firstLine > 0) {
				int maxlen = pageText.length();
				if (maxlen > MAX_CHARACTERS) {
					maxlen = MAX_CHARACTERS;
				}
				pageText = renderingManager.textToHTML(pageText.substring(firstLine + 1, maxlen).trim());
				if (maxlen == MAX_CHARACTERS) {
					pageText += "...";
				}
				e.setContent(pageText);
			} else {
				e.setContent(title);
			}
			e.setAuthor(getAuthor(page));
			feed.addEntry(e);
		}

		return feed.getString();
	}

	@Override
	public void handleEvent(Event event) {
		String topic = event.getTopic();
		switch (topic) {
		// Initialize.
		case EngineEvent.Topic.INIT_STAGE_TWO:
			try {
				initializeStageTwo();
			} catch (WikiException e) {
				log.error("Failed initialization of DefaultRSSGeneratorManager.", e);
			}
			break;
		}
	}

}
